/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

#include "metal.h"

using namespace metal;
using namespace metal::meta;
using namespace std;

#define TEST(x) void x##_test(); void x() { cout << #x << endl; x##_test(); } void x##_test()

namespace {
  ident(x);
  ident(y);
  ident(f);
  ident(g);
  ident(h);
}

deftype(foo,
	mem<int>(x),
	mem<double>(y));

typedef structure<binding<meta_x, int>, binding<meta_y, double> > ex_type;

static_assert(is_same<foo, ex_type>::value, 
	      "deftype() did not compute the expected type"); 

namespace {
  using namespace detail;

  typedef struct_member<double, meta_y, void> member1;
  typedef struct_member<int, meta_x, member1> member2;
  typedef member2 s_type;

  typedef struct_instance<s_type> i_type;

  typedef struct_member<i_type *, meta_x, void> ns_type;
  typedef struct_instance<ns_type> ni_type;

  template<typename Ex, typename T>
  void check(T t) {
    static_assert(is_same<T, Ex>::value, "");
  }

  TEST(member_reference_tests) {
    auto inst = new i_type;
    struct_ref<i_type *> ref(move(*inst));
  
    typedef ref_member_type_of<i_type *, meta_x>::type x_mem_type;
    static_assert(is_same<x_mem_type, int&>::value, "");
  
    static_assert(is_same<decltype(inst->get<meta_x>()), int &>::value,
		  "wrong type");
    static_assert(is_same<decltype(inst->get<meta_y>()), double &>::value,
		  "wrong type");
    static_assert(is_same<decltype(ref[&x]), int &>::value,
		  "wrong type");
    static_assert(is_same<decltype(ref[&y]), double &>::value,
		  "wrong type");
  
    auto n_inst = new ni_type;
    struct_ref<ni_type *> n_ref(move(*n_inst));
  
    // check<i_type *&>(inst->get<meta_x>());
    static_assert(is_same<decltype(n_inst->get<meta_x>()), i_type *&>::value, 
		  "wrong type");
    static_assert(is_same<decltype(n_ref[&x]), struct_ref<i_type *&> >::value,
		  "wrong type");
  }

  TEST(ref_1_test) {
    auto inst = new i_type;
    assert(inst->ref_count() == 1);
    
  }
  
  TEST(rval_ref_cons) {   
    auto inst = new i_type;
    struct_ref<i_type *> ref(move(*inst));
    assert(ref.get() == inst);
    assert(inst->ref_count() == 1);
  }
  
  TEST(rval_ref_assign) {
    auto inst = new i_type;
    struct_ref<i_type *> ref1;
    ref1 = move(*inst);
    assert(ref1.get() == inst);
    assert(inst->ref_count() == 1);
  }

  TEST(ref_cons_from_ref) { 
    auto inst = new i_type;
    struct_ref<i_type *> ref0(move(*inst));
    struct_ref<i_type *> ref1(ref0);
    assert(ref1.get() == inst);
    assert(inst->ref_count() == 2);
  }

  TEST(ref_assign_from_ref) {
    auto inst = new i_type;
    struct_ref<i_type *> ref0(move(*inst));
    struct_ref<i_type *> ref1;
    ref1 = ref0;
    assert(ref1.get() == inst);
    assert(inst->ref_count() == 2);
  }

  TEST(ref_cons_from_ptr_ref) {
    auto inst = new i_type;

    i_type *ptr = 0;
    struct_ref<i_type *&> pref(ptr);
    inst->ref();
    ptr = inst;

    assert(inst->ref_count() == 2);

    struct_ref<i_type *> ref(pref);
    assert(ref.get() == inst);
    assert(inst->ref_count() == 3);
  }

  TEST(ref_assign_from_ptr_ref) {
    auto inst = new i_type;

    i_type *ptr = 0;
    struct_ref<i_type *&> pref(ptr);
    inst->ref();
    ptr = inst;

    struct_ref<i_type *> ref;
    ref = pref;
    assert(ref.get() == inst);
    assert(inst->ref_count() == 3);
  }

  TEST(ref_self_assign) {
    auto inst = new i_type;
    struct_ref<i_type *> ref(move(*inst));
    ref = ref;
    assert(inst->ref_count() == 1);
  }

  TEST(ptr_ref_assign_from_ref) {
    auto inst = new i_type;
    struct_ref<i_type *> ref(move(*inst));

    i_type *ptr = 0;
    struct_ref<i_type *&> pref(ptr);
    pref = ref;

    assert(pref.get() == inst);
    assert(ptr == inst);
    assert(inst->ref_count() == 2);
  }

  TEST(ptr_ref_assign_from_ptr_ref) {
    auto inst = new i_type;
    struct_ref<i_type *> ref(move(*inst));

    i_type *ptr = 0;
    struct_ref<i_type *&> pref0(ptr);
    pref0 = ref;

    i_type *ptr1 = 0;
    struct_ref<i_type *&> pref1(ptr1);
    pref1 = pref0;

    assert(pref1.get() == inst);
    assert(ptr1 == inst);
    assert(inst->ref_count() == 3);
  }
  
  TEST(ptr_ref_self_assign) { 
    auto inst = new i_type;

    i_type *ptr = 0;
    struct_ref<i_type *&> pref(ptr);
    pref = inst;

    pref = pref;

    assert(pref.get() == inst);
    assert(ptr == inst);
    assert(inst->ref_count() == 2);
  }

  TEST(nested_struct_access_test) {
    auto inst = new ni_type;
    struct_ref<ni_type *> ref = wrap(move(*inst));

    struct_ref<i_type *&> ref_x = ref[&x];
    assert(inst->ref_count() == 1);
    assert(ref_x.get() == 0);

    auto nx = new i_type;
    ref_x = nx;
    nx->deref();

    assert(nx->ref_count() == 1);
    assert(ref_x.get() == nx);
    assert(ref[&x].get() == nx);
  }

  TEST(nested_tests) {
    // nested struct
    typedef struct_member<int, meta_x, struct_member<struct_instance<s_type> *, meta_y, void> > ns_type;
    typedef member_type_of<ns_type, meta_x>::type nx_type;
    typedef member_type_of<ns_type, meta_y>::type ny_type;
    
    typedef struct_instance<s_type>* ny_expected_type;
    
    static_assert(is_same<nx_type, int>::value, "scalar-typed member x");
    static_assert(is_same<ny_type, ny_expected_type>::value, "s-typed member y");
    
    cout << "instantiating" << endl;
    auto ns_inst = new struct_instance<ns_type>;
    cout << "wrapping" << endl;
    auto ns_ref = wrap(move(*ns_inst));

    auto inst = new struct_instance<s_type>;
    auto ref = wrap(move(*inst));

    cout << "accessing" << endl;
    struct_ref<struct_instance<s_type> *&> n_ref = ns_ref[&y];
    cout << "constructed n_ref" << endl;

    n_ref = inst;
    cout << "reassigned n_ref" << endl;
    
    ns_ref[&x] = 3;
    cout << "set = 3" << endl;

    assert(ns_ref[&x] == 3);
    
    ns_ref[&y] = ref;
    cout << "" << endl;
    static_assert(is_same<decltype(ns_ref[&y]), struct_ref<struct_instance<s_type> *&> >::value, "");
    ref = ns_ref[&y];
    cout << "#" << endl;
    
    auto  a=ns_ref[&y];
    cout << "@" << endl;
    
    auto b=ns_ref[&y][&x];
    cout <<"1"<<endl;
    ns_ref[&y][&x] = 2;
    
    cout <<"2"<<endl;
    assert(ns_ref[&y][&x] == 2);
    cout <<"3"<<endl;
    assert(ref[&x] == 2);
    cout <<"4"<<endl;
  }
}

TEST(struct_tests) {
  member_reference_tests();
  ref_1_test();
  rval_ref_cons();
  rval_ref_assign();
  ref_cons_from_ref();
  ref_assign_from_ref();
  ref_cons_from_ptr_ref();
  ref_assign_from_ptr_ref();
  ref_self_assign();
  ptr_ref_assign_from_ref();
  ptr_ref_assign_from_ptr_ref;
  ptr_ref_self_assign();
  nested_struct_access_test();
  nested_tests();

  cout << "struct_tests" << endl;

  mod(s_test,

      f = def() ( cons<foo>(x = 99, y = 3.0) ),

      g = def() ( f()[&x] ),

      h = def(param(foo, x)) ( x[&x] )

      );

  auto program = link(s_test);

  typedef struct_member<int, meta_x,
    struct_member<double, meta_y, void> > foo_spec;
  typedef struct_instance<foo_spec> &&r_type;
  typedef r_type (*foo_func)();

  foo_func f_call = program.call(f);

  auto f_ref = wrap(f_call());

  assert(f_ref.get()->ref_count() == 1);  
  assert(program.call(g)() == 99);
}
